package vn.mahjonggame.game.request;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.log4j.Logger;

import vn.mahjonggame.game.Mahjong;
import vn.mahjonggame.game.MahjongExtension;
import vn.mahjonggame.metadata.GameActionType;
import vn.mahjonggame.metadata.MahjongCache;
import vn.mahjonggame.metadata.MatchState;
import vn.mahjonggame.metadata.Player;
import vn.mahjonggame.metadata.MObject;
import vn.mahjonggame.metadata.Response;
import vn.mahjonggame.metadata.RoomVarName;
import vn.mahjonggame.metadata.Tile;
import vn.mahjonggame.metadata.TileSet;
import vn.mahjonggame.platform.GameplayConfig;
import vn.mahjonggame.platform.PlatformConfig;
import vn.mahjonggame.util.Common;
import vn.mahjonggame.util.PlayerComparator;

import com.smartfoxserver.v2.entities.Room;
import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSArray;
import com.smartfoxserver.v2.entities.data.SFSObject;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import com.smartfoxserver.v2.extensions.ExtensionLogLevel;

public class ThrowTileHandler extends BaseClientRequestHandler {
		
	@Override
	public void handleClientRequest(User user, ISFSObject params) {
		MahjongExtension ext = (MahjongExtension) getParentExtension();
				
		throwTile(ext, user, params, false, false);
	}
	
	public void throwTile(final MahjongExtension ext, User user, ISFSObject params, boolean isAuto, boolean isThrownAfterKan){
		
		this.setParentExtension(ext);
		Mahjong mahjong = ext.getMahjong();
		Player player = mahjong.getPlayerById(user.getId());
		Logger logger = mahjong.getLogger();
		Room room = ext.getGameRoom();		
		final List<User> users = room.getUserList();
		List<Player> players = mahjong.getPlayers();
		
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "isAuto: " + isAuto));
		
		if(player == null){
			logger.info("Player " + user.getName() + "("+user.getId()+") did not join game");
			return;
		}
		
		int bonusTime = 0;		
		if(!isAuto){
			bonusTime = params.getInt(MObject.bonusTime.toString());
		}
		
		int tileId = params.getInt(MObject.tile.toString());
		Tile tile = mahjong.getTileById(tileId);			
		
		if(!isAuto){
			player.cancelTimer();
		}		
		
		/*
		 * Reset the skipTypes of all players
		 */
		for(Iterator<Player> iPlayers = players.iterator(); iPlayers.hasNext();){
			Player iPlayer = (Player)iPlayers.next();
			iPlayer.getSkipTypes().clear();
		}
		
		/*
		 * Reset the mahjong info
		 */
		mahjong.setCanCallChiPlayer(null);
		mahjong.setCanCallPonPlayer(null);
		mahjong.setCanCallOpenKanPlayer(null);
		mahjong.getCanCallRonPlayers().clear();
		
		Player preThrownPlayer = mahjong.getThrownPlayer();
		Player playerInTurn = mahjong.getPlayerInTurn();
		
		int preThrownPlayerPosition = 3;
		
		if(preThrownPlayer != null){
			preThrownPlayerPosition = preThrownPlayer.getPlayerPosition();
		}		
		
		if(!player.equals(playerInTurn)){
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "playerInTurn is " + playerInTurn.toString()));			
			return;
		}
		
		if(player.isHasThrown()){
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "has thrown."));			
			return;
		}
		
		List<Tile> leftTiles = player.getLeftTiles();
		List<Tile> thrownTiles = player.getThrownTiles();
		
		/*
		 * Get the next player
		 */
		Player nextPlayer = mahjong.getNextPlayer(player);
		
		if(nextPlayer == null){
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "nextPlayer is null"));
			return;
		}
		
		int numTimesCallKan = player.getNumTimesCallKan();
		List<Tile> doras = new ArrayList<Tile>();
		if(isThrownAfterKan){						
			doras.addAll(mahjong.getDoras().subList(mahjong.getDoras().size() - numTimesCallKan, mahjong.getDoras().size()));			
			logger.info(mahjong.log(room, player, GameActionType.throwTileAfterKan.toString(), "doras: " + doras));			
			player.setNumTimesCallKan(0);
		}
				
		/*
		 * Update the list of thrown tile of this player
		 */
		leftTiles.remove(tile);
		thrownTiles.add(tile);
				
		/*
		 * Sort players
		 */
		PlayerComparator comparator = new PlayerComparator();
		Collections.sort(players, comparator);
				
		/*
		 * Build the response in case HOA
		 */
		Response.Builder responseBuilder = new Response.Builder();
		
		Response.Builder subResponseBuilder = new Response.Builder();
		subResponseBuilder.losePos(player.getPlayerPosition());
		subResponseBuilder.loseTile(tileId);
				
		/*
		 * Check if the mahjong is HOA
		 */
		for(Iterator<Player> iPlayers = mahjong.getPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = iPlayers.next();
			logger.info(iPlayer.toString() + ": " + iPlayer.getThrownTiles());
		}
				
		if(mahjong.is4Gio()){
			
			SFSArray sfsPlayerArray = new SFSArray();
			
			for(int i = 0; i < players.size(); i++){
				
				Player iPlayer = players.get(i);
				
				Response.Builder sfsUser = new Response.Builder();
				
				sfsUser.name(iPlayer.getName())
						.point(0)
						.money(0)
						.isWinner(false)
						.tiles(mahjong.tileArrayToTileIdArray(iPlayer.getLeftTiles()));
				
				sfsPlayerArray.addSFSObject(sfsUser.build().getSFSObject());					
				logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "HOA Player " + iPlayer.toString() + " " + iPlayer.getLeftTiles()));				
			}
			
			subResponseBuilder.winPos(player.getPlayerPosition())
								.winTile(Arrays.asList(tileId))
								.type(1);			
			responseBuilder.HOA(subResponseBuilder.build().getSFSObject())
							.players(sfsPlayerArray);					
			
			send(TileSet.HOA.toString(), responseBuilder.build().getSFSObject() , users);
			ext.setMatchState(MatchState.SHOW_RESULT);			
			
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Send HOA cmd"));
			return;
		}			
		
		/*
		 * Check if the mahjong is NORMAL HOA
		 */		
		int numPickedDeathTile = mahjong.getPickedDeathTile().size();
		int playerNumPickedDeathTile = player.getPickedDeathTile().size();
		
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "PickedDeathTile: " + mahjong.getPickedDeathTile()));
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "PlayerPickedDeathTile: " + player.getPickedDeathTile()));
		
		if(numPickedDeathTile == 3 
				&& playerNumPickedDeathTile > 0 
				&& playerNumPickedDeathTile < numPickedDeathTile 
				&& mahjong.getCanCallRonPlayers().size() == 0 
				&& player.isRiichiWaiting()){
			

			SFSArray sfsPlayerArray = new SFSArray();
			
			for(int i = 0; i < players.size(); i++){
				
				Player iPlayer = players.get(i);
				
				Response.Builder sfsUser = new Response.Builder();
				sfsUser.name(iPlayer.getName())
						.point(0)
						.money(0)
						.isWinner(false)
						.tiles(mahjong.tileArrayToTileIdArray(iPlayer.getLeftTiles()));
				
				sfsPlayerArray.addSFSObject(sfsUser.build().getSFSObject());
				
				logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "NORMAL HOA Player " + iPlayer.toString() + " " + iPlayer.getLeftTiles()));
			}
			
			subResponseBuilder.winPos(player.getPlayerPosition())
								.winTile(Arrays.asList(tileId))
								.type(1);
			
			responseBuilder.HOA(subResponseBuilder.build().getSFSObject())
							.players(sfsPlayerArray);
						
			send(TileSet.HOA.toString(), responseBuilder.build().getSFSObject(), users);			
			
			ext.setMatchState(MatchState.SHOW_RESULT);			
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Mahjong is NORMAL HOA"));
			
			return;
		}
		
		/*
		 * Check if any next player can call RON
		 */
		for(int i = 0; i < players.size(); i++){
			Player nextPlayerTmp = players.get(i);			
			if(nextPlayerTmp.isDisconnect() || nextPlayerTmp.equals(player)) continue;
						
			nextPlayerTmp.checkTiles(tile);
			nextPlayerTmp.chiitoisu();
			nextPlayerTmp.kokushiMusou(tile);
			
			boolean isComplete = false;
						
			if(nextPlayerTmp.isSpecialComplete()){
				isComplete = true;
			}
			else if (nextPlayerTmp.isNormalComplete()){
				mahjong.calculateHanScore(nextPlayerTmp, tile);
				if(nextPlayerTmp.getYakuList().size() > 0){
					isComplete = true;
				}
			}			
			
			if(isComplete){
				nextPlayerTmp.getSkipTypes().add(TileSet.RON);				
				mahjong.getCanCallRonPlayers().add(nextPlayerTmp);							
			}					
		}
		
		/*
		 * Check if any next player can call OPEN KAN
		 */
		for(int i = 0; i < players.size(); i++){
			Player nextPlayerTmp = players.get(i);			
			if(nextPlayerTmp.isDisconnect() || nextPlayerTmp.equals(player)) continue;
						
			if(nextPlayerTmp.checkKan(tile) && !nextPlayerTmp.isDisconnect()){				
				nextPlayerTmp.getSkipTypes().add(TileSet.OPENKAN);				
				mahjong.setCanCallOpenKanPlayer(nextPlayerTmp);				
				break;
			}			
		}
		
		/*
		 * Check if any next player can call PON
		 */
		for(int i = 0; i < players.size(); i++){
			Player nextPlayerTmp = players.get(i);			
			if(nextPlayerTmp.isDisconnect() || nextPlayerTmp.equals(player)) continue;
						
			if(nextPlayerTmp.checkPon(tile) && !nextPlayerTmp.isDisconnect()){
				nextPlayerTmp.getSkipTypes().add(TileSet.PON);							
				mahjong.setCanCallPonPlayer(nextPlayerTmp);				
				break;
			}			
		}
		
		/*
		 * Check if any next player can call CHI
		 */		
		List<List<Tile>> tileIdss = nextPlayer.checkChi(tile);
		
		if(tileIdss.size() > 0 && !nextPlayer.isDisconnect()){				
			mahjong.setCanCallChiPlayer(nextPlayer);
			nextPlayer.getSkipTypes().add(TileSet.CHI);					
		}
		
		//FIXME Need to build a common method for this
		List<Integer> canCallRonPlayerPositions = new ArrayList<Integer>();		
		for(Iterator<Player> iPlayers = mahjong.getCanCallRonPlayers().iterator(); iPlayers.hasNext();){
			Player iPlayer = (Player)iPlayers.next();
			canCallRonPlayerPositions.add(iPlayer.getPlayerPosition());
		}
					
		/*
		 * Build the response in case RON/OPENKAN/PON/CHI
		 */
		Player canCallChiPlayer = mahjong.getCanCallChiPlayer();
		Player canCallPonPlayer = mahjong.getCanCallPonPlayer();
		Player canCallOpenKanPlayer = mahjong.getCanCallOpenKanPlayer();
		List<Player> canCallRonPlayers = mahjong.getCanCallRonPlayers();
		
		for(Iterator<User> iUsers = users.iterator(); iUsers.hasNext();){
			User iUser = iUsers.next();
			Player iPlayer = mahjong.getPlayerById(iUser.getId());
			
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "iPlayer: " + iPlayer.toString()));
			
			Response.Builder lResponseBuilder = new Response.Builder();
			Response.Builder lSubResponseBuilder = null;
			
			lResponseBuilder.position(player.getPlayerPosition())
							.nextPosition(nextPlayer.getPlayerPosition())
							.tile(tileId)
							.preTurn(preThrownPlayerPosition)
							.isAuto(isAuto);
			
			if(isThrownAfterKan){
				lResponseBuilder.doras(mahjong.tileArrayToTileIdArray(doras));
			}
						
			if(!iPlayer.isRiichiWaiting() && (iPlayer.equals(canCallChiPlayer) 
					|| iPlayer.equals(canCallPonPlayer)
					|| iPlayer.equals(canCallOpenKanPlayer)
					|| canCallRonPlayers.contains(iPlayer))){
				
				lResponseBuilder.turn(iPlayer.getPlayerPosition())								
								.turnTime(iPlayer.getTurnTime()/1000)
								.bonusTime(iPlayer.getBonusTime()/1000);
				
				
				if(canCallRonPlayers.contains(iPlayer)){
					lSubResponseBuilder = new Response.Builder();
					lSubResponseBuilder.winPos(canCallRonPlayerPositions)
										.winTile(Arrays.asList(tileId));
					lResponseBuilder.RON(lSubResponseBuilder.build().getSFSObject());
				}
				
				if(iPlayer.equals(canCallOpenKanPlayer)){
					lSubResponseBuilder = new Response.Builder();
					lSubResponseBuilder.winPos(iPlayer.getPlayerPosition())
										.winTile(Arrays.asList(tileId, tileId, tileId));
					lResponseBuilder.OPENKAN(lSubResponseBuilder.build().getSFSObject());					
				}				
				else if(iPlayer.equals(canCallPonPlayer)){
					lSubResponseBuilder = new Response.Builder();
					lSubResponseBuilder.winPos(iPlayer.getPlayerPosition())
										.winTile(Arrays.asList(tileId, tileId));
					lResponseBuilder.PON(lSubResponseBuilder.build().getSFSObject());
				}
				
				if(iPlayer.equals(canCallChiPlayer)){
					
					SFSArray tileIdSFSArray = new SFSArray();
					for(int i = 0; i < tileIdss.size(); i++){
						List<Tile> tileIds = tileIdss.get(i);
						
						tileIdSFSArray.addIntArray(mahjong.tileArrayToTileIdArray(tileIds));
					}
					
					lSubResponseBuilder = new Response.Builder();
					lSubResponseBuilder.winPos(iPlayer.getPlayerPosition())
										.winTile(tileIdSFSArray);
									
					lResponseBuilder.CHI(lSubResponseBuilder.build().getSFSObject());
				}								
			}
			else {
				lResponseBuilder.turn(nextPlayer.getPlayerPosition())								
								.turnTime(nextPlayer.getTurnTime()/1000)
								.bonusTime(nextPlayer.getBonusTime()/1000);				
			}
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Response: " + lResponseBuilder.build().toString()));
			send(isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), lResponseBuilder.build().getSFSObject(), iUser);
		}
		
		if(player.isTempFuriten()){
			player.setTempFuriten(false);
		}
		
		mahjong.setThrownPlayer(player);
		mahjong.setPlayerInTurn(nextPlayer);
		mahjong.setHasPickedTile(false);
		
		player.setJustThrownTile(tile);
				
		/*
		 * Roll back if RON is ignored
		 */
		player.setChiiToitsu(false);
		player.setKokushiMusou13Han(false);
		player.setKokushiMusou26Han(false);
		
		/*
		 * Roll back if RIICHI is ignored
		 */
		player.setTenpai(false);
		player.setRiichi(false);			
		
		nextPlayer.setHasPicked(false);
		player.setHasThrown(true);
		player.setTsumo(false);
		
		player.updateBonusTime();
		
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Bonus time from client: " + bonusTime * 1000));			
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Bonus time from server: " + player.getBonusTime()));
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "startTime: " + player.getStartTime()));
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "leftTime: " + (player.getEndTime() - player.getStartTime())));			
		
		nextPlayer.setStartTime(System.currentTimeMillis());
				
		if(user != null){
			ext.addAction(user, GameActionType.throwTile.toString());
		}		
		
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "thrownTile: " + tile.toString()));
		logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "nextTurnPlayer: " + nextPlayer.toString()));
		
		/*
		 * Get the number of player can call RON/OPENKAN/PON/CHI 
		 */
		int numPlayerCanCallTileSet = 0;
		
		for(Iterator<Player> iPlayers = players.iterator(); iPlayers.hasNext();){
			Player iPlayer = (Player)iPlayers.next();
			if(iPlayer.getSkipTypes().size() > 0)
				numPlayerCanCallTileSet++;
		}
				
		
		/*
		 * If the next turn player is disconnected, wait 1 second, then pickTile for that player
		 */				
		if(nextPlayer.isDisconnect() && numPlayerCanCallTileSet == 0){			
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), nextPlayer.toString() + " is disconnected => Auto"));
			final User nextUser = mahjong.getUserByPlayerId(nextPlayer.getPlayerId(), users);
			logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), nextUser.toString()));
			Timer timer = nextPlayer.getTimer();
			timer.schedule(new TimerTask() {
				  @Override
				  public void run() {
					  PickTileHandler pickTileHandler = new PickTileHandler();
					  pickTileHandler.pickTile(ext, nextUser, new SFSObject(), false);
				  }
			}, 1000);
			return;
		}
		
		if(Boolean.valueOf(room.getVariable(RoomVarName.is_auto.toString()).getStringValue())){
			/*
			 * If the next turn player can call KAN/CHi/PON
			 */			
			int delay = Integer.valueOf(room.getVariable(RoomVarName.turn_time.toString()).getStringValue());
			
			for(Iterator<User> iUsers = user.getLastJoinedRoom().getUserList().iterator(); iUsers.hasNext();){
				User iUser = (User)iUsers.next();
				Player iPlayer = mahjong.getPlayerById(iUser.getId());
				
				if(iPlayer.getSkipTypes().size() > 0){
					if(iPlayer.getSkipTypes().size() > 1){
						iPlayer.setConflict(true);
					}
					
					logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "iPlayer: " + iPlayer.toString()));
					logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "SkipTypes: " + iPlayer.getSkipTypes()));
					logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Conflict: " + iPlayer.isConflict()));
					logger.info(mahjong.log(room, player, isThrownAfterKan ? GameActionType.throwTileAfterKan.toString() : GameActionType.throwTile.toString(), "Calling skip handler: " + delay));
					
					Response.Builder skipResponse = new Response.Builder();
					skipResponse.type(iPlayer.getSkipTypes().get(0).toString());								
					
					final SFSObject nextParams = skipResponse.build().getSFSObject();					
					final User finalUser = iUser;
					
					Timer timer = iPlayer.getTimer();
					timer.schedule(new TimerTask() {
						  @Override
						  public void run() {
							  SkipHandler skipHandler = new SkipHandler();
							  skipHandler.skip(ext, finalUser, nextParams, true);								
						  }
					}, delay);
				}
			}
		}		
	}
}